package org.eclipse.gittab.fretboard.draw2d.painters;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.XYAnchor;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gittab.fretboard.FretboardDesign;
import org.eclipse.gittab.fretboard.Fretboard.Configuration;
import org.eclipse.gittab.fretboard.Fretboard.FretboardListener;
import org.eclipse.gittab.fretboard.FretboardDesign.FretHeight;
import org.eclipse.gittab.fretboard.draw2d.figures.FretFigure;
import org.eclipse.gittab.fretboard.draw2d.figures.ModernFretInlayFigure;
import org.eclipse.gittab.fretboard.draw2d.figures.NoteInfoFigure;
import org.eclipse.gittab.fretboard.draw2d.figures.StringLabel;
import org.eclipse.gittab.fretboard.draw2d.figures.StringLine;
import org.eclipse.gittab.fretboard.draw2d.figures.TextInfoFigure;
import org.eclipse.gittab.fretboard.draw2d.figures.ModernFretInlayFigure.FretInlay;
import org.eclipse.gittab.fretboard.internal.IFretboardPainter;
import org.eclipse.gittab.fretboard.internal.IFretboardPainter.PaintContext.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

/**
 * Draws a guitar fretboard from left to right using <i>Draw2d</i>.
 * 
 * @author mtreitle
 * 
 */
public abstract class FretboardPainter implements IFretboardPainter,
		MouseListener {

	public static final int BORDER_LEFT = 20;
	public static final int BORDER_TOP = 10;

	public static final int TUNING_WIDTH = 20;

	public static final float WIDTH_LOW_STRING = 5.0f;
	public static final float WIDTH_HIGH_STRING = 3.0f;

	public static final float WIDTH_LOW_FRET = 50;
	public static final float WIDTH_HIGH_FRET = 30;

	public static final int FRET_HEIGHT_CLASSIC = 140;
	public static final int FRET_HEIGHT_MODERN = 120;

	/**
	 * Matrix that stores the {@link StringLine} figures.
	 * <p>
	 * First dimension is the fret number. Second dimension is the string
	 * number, starting with the lowest string.
	 */
	protected StringLine[][] stringLines;

	protected StringLabel[] stringLabels;

	private final FretboardListener fbListener;

	public FretboardPainter(FretboardListener listener) {
		this.fbListener = listener;
	}

	@Override
	public void clean() {

		if (stringLines != null) {
			for (StringLine[] strings : stringLines) {
				for (StringLine string : strings) {
					string.removeMouseMotionListener(stringLineHoverAdapter);
					string.removeMouseListener(this);
				}
			}
		}

		if (stringLabels != null) {
			for (StringLabel stringLabel : stringLabels) {
				stringLabel.removeMouseListener(this);
			}
		}

		if (composite != null) {
			composite.dispose();
		}
	}

	private final IFigure createContents(Composite parent) {

		composite = new Composite(parent, SWT.NONE);
		composite.setLayout(new FillLayout());

		final Canvas canvas = new Canvas(composite, SWT.H_SCROLL);

		LightweightSystem lws = new LightweightSystem(canvas);

		IFigure contents = new Figure();
		contents.setOpaque(true);

		// using a null layout manager
		// contents.setLayoutManager(new XYLayout());

		lws.setContents(contents);

		return contents;
	}

	@Override
	public void paintConfigurationInvalid(PaintContext ctx) {

		final IFigure contents = createContents(ctx.getConfiguration()
				.getComposite());

		final FretboardDesign design = ctx.getDesign();

		TextInfoFigure textInfo = new TextInfoFigure(CONFIG_INVALID,
				ColorConstants.black, design.stringLabelFont());

		textInfo.setLocation(new Point(BORDER_LEFT, BORDER_TOP));

		textInfo.setSize(1000, textInfo.getHeight());

		contents.add(textInfo);

		contents.setBackgroundColor(design.background());

		// re"draw" the parent composite
		ctx.getConfiguration().getComposite().layout();
	}

	/**
	 * Paints the whole fretboard, including the strings and the tuning (open
	 * strings).
	 */
	@Override
	public void paint(PaintContext ctx) {

		final IFigure contents = createContents(ctx.getConfiguration()
				.getComposite());

		final FretboardDesign design = ctx.getDesign();
		final Configuration config = ctx.getConfiguration();

		stringLineHoverAdapter.setHoverColor(design.hoverNote());

		final String[][] notesMatrix = config.getNotes();

		final int fretHeight = getFretHeight(design);

		final int stringsTotal = Util.nrOfStrings(ctx);
		final int fretsTotal = Util.nrOfFrets(ctx);

		// also reused later when drawing the tunings
		int startx = getFretsPos();

		final float strWidthIncr = (WIDTH_LOW_STRING - WIDTH_HIGH_STRING)
				/ stringsTotal;
		final float startStrWidth = WIDTH_LOW_STRING;
		float strWidth = 0.0f;

		final double[] stringLineYPos = calcStringYPos(fretHeight, stringsTotal);

		double fretX = startx;

		final double[] fretsWidth = calcWidthOfFrets(fretsTotal);

		stringLines = new StringLine[fretsTotal][stringsTotal];

		for (int f = 0; f < fretsTotal; f++) {

			final double fretWidth = fretsWidth[f];

			FretFigure fret = new FretFigure(fretWidth, fretHeight, design
					.fretboard(), design.frets());

			fret.setLocation(new Point(fretX, BORDER_TOP));

			if (drawInlay(f)) {

				ModernFretInlayFigure inlay = new ModernFretInlayFigure(
						getModernFretInlay(fret));

				// TODO
				// ClassicFretInlayFigure inlay = new
				// ClassicFretInlayFigure(fret
				// .getBounds(), f % 12 == 0);

				inlay.setBackgroundColor(ColorConstants.white);
				fret.add(inlay);
			}

			// now add string lines

			strWidth = startStrWidth;
			// starting with the lowest string
			for (int s = 0; s < stringsTotal; s++) {

				StringLine stringLine = new StringLine(design.strings(),
						strWidth);
				strWidth = strWidth - strWidthIncr;

				// TODO string muss auch fretborder uebermalen
				final double stringY = stringLineYPos[s];

				stringLine.setSourceAnchor(new XYAnchor(new Point(fretX,
						stringY)));
				stringLine.setTargetAnchor(new XYAnchor(new Point(fretX
						+ fretWidth, stringY)));

				stringLines[f][s] = stringLine;

				fret.add(stringLine);

				int stringIdx = s;
				int fretIdx = getFretIndexByNr(f + 1);

				// now add the note as information for the figure
				NoteInfoFigure marker = new NoteInfoFigure(
						notesMatrix[stringIdx][fretIdx + 1], fretIdx + 1,
						stringIdx + 1);

				stringLine.addNoteInfoFigure(marker);

				// add mouse listeners
				stringLine.addMouseMotionListener(stringLineHoverAdapter);
				stringLine.addMouseListener(this);
			}

			contents.add(fret);

			fretX = fretX + fretWidth;
		}

		startx = getTuningPos();
		// paint tuning
		stringLabels = new StringLabel[stringsTotal];

		if (stringLineYPos.length == config.getStringsNr()) {

			for (int s = 0; s < stringsTotal; s++) {

				StringLabel label = new StringLabel(s + 1, notesMatrix[s][0],
						design.stringLabelColor(), design.stringLabelFont());

				int fontHeight = label.getHeight();
				label.setSize(TUNING_WIDTH, fontHeight);
				// how calculate font width?
				label.setLocation(new Point(startx, stringLineYPos[s]
						- fontHeight / 2));

				label.addMouseListener(this);

				stringLabels[s] = label;

				contents.add(label);
			}
		}

		contents.setBackgroundColor(design.background());

		// re"draw" the parent composite
		ctx.getConfiguration().getComposite().layout();
	}

	/**
	 * Gets the direction specific {@link FretInlay} figure.
	 * 
	 * @return
	 */
	protected abstract FretInlay getModernFretInlay(FretFigure fret);

	/**
	 * Calculates the width of all frets when drawing from left to right.
	 * 
	 * @param fretsNr
	 * @return
	 */
	protected abstract double[] calcWidthOfFrets(final int fretsTotal);

	/**
	 * Gets the position (x-coordinate) where to draw the frets.
	 * 
	 * @return
	 */
	protected abstract int getFretsPos();

	/**
	 * Gets the position (x-coordinate) where to draw the tuning.
	 * 
	 * @return
	 */
	protected abstract int getTuningPos();

	/**
	 * Gets the index for the notes matrix (see {@link Configuration#getNotes()}
	 * ) that is equivalent to the drawn fret number. </p> The frets are
	 * numbered from 1 to {@link Configuration#getFretsNr()}.
	 * 
	 * @param fretNr
	 * @return
	 */
	protected abstract int getFretIndexByNr(int fretNr);

	private NoteInfoFigure getNoteInfoFigure(int fret, int string) {

		int fretIdx = getFretIndexByNr(fret);

		return stringLines[fretIdx][string].getNoteInfoFigure();
	}

	@Override
	public void select(int fret, int string, Color color) {

		NoteInfoFigure noteInfo = getNoteInfoFigure(fret, string);

		noteInfo.setBackgroundColor(color);
		noteInfo.setVisible(true);
	}

	@Override
	public void deSelect(int fret, int string) {

		NoteInfoFigure noteInfo = getNoteInfoFigure(fret, string);

		noteInfo.setVisible(false);
	}

	@Override
	public Color getSelection(int fret, int string) {

		NoteInfoFigure noteInfo = getNoteInfoFigure(fret, string);

		if (noteInfo.isVisible()) {
			return noteInfo.getBackgroundColor();
		}
		return null;
	}

	/**
	 * Calculate the y-Position of a string. </p> Note: the lowest (in terms of
	 * tone) string is drawn bottom and the highest string top.
	 * 
	 * @param fretHeight
	 * @return
	 */
	private double[] calcStringYPos(int fretHeight, int nrOfStrings) {

		// space between strings
		double space = fretHeight / nrOfStrings;

		double y = BORDER_TOP + fretHeight - space / 2;
		double[] stringLineYPos = new double[nrOfStrings];

		for (int s = 0; s < nrOfStrings; s++) {
			stringLineYPos[s] = y;
			y = y - space;
		}

		return stringLineYPos;
	}

	/**
	 * Gets the absolute fret height determined by the {@link FretHeight}
	 * enumeration.
	 * 
	 * @param design
	 * @return
	 */
	private int getFretHeight(FretboardDesign design) {

		if (design.fretHeight() == FretHeight.CLASSIC) {
			return FRET_HEIGHT_CLASSIC;
		}
		return FRET_HEIGHT_MODERN;
	}

	/**
	 * Calculates if an inlay shall be drawn on an absolute fret position.
	 * 
	 * @param fretPos
	 * @return
	 */
	private boolean drawInlay(int fretPos) {

		fretPos = (fretPos + 1) % 12;

		return fretPos == 0 || fretPos == 3 || fretPos == 5 || fretPos == 7
				|| fretPos == 9 || fretPos == 12;
	}

	@Override
	public void mouseDoubleClicked(MouseEvent me) {
	}

	@Override
	public void mousePressed(MouseEvent me) {

		Object source = me.getSource();
		int fret = 0;
		int string = 0;

		if (source instanceof StringLine) {
			NoteInfoFigure noteInfoFigure = ((StringLine) source)
					.getNoteInfoFigure();
			fret = noteInfoFigure.getFret();
			string = noteInfoFigure.getString();

		} else if (source instanceof StringLabel) {
			string = ((StringLabel) source).getString();
		}

		fbListener.clickedOn(fret, string);
	}

	@Override
	public void mouseReleased(MouseEvent me) {
	}

	private final StringLineHoverAdapter stringLineHoverAdapter = new StringLineHoverAdapter();
	private Composite composite;

	/**
	 * Adapter that shows the {@link NoteInfoFigure} when hovering over a
	 * {@link StringLine} figure.
	 * 
	 * @author mtreitle
	 * 
	 */
	class StringLineHoverAdapter implements MouseMotionListener {

		private boolean hideOnExit;

		private Color color;

		public StringLineHoverAdapter() {
		}

		public void setHoverColor(Color color) {
			this.color = color;
		}

		/**
		 * Shows the {@link NoteInfoFigure} if the {@link NoteInfoFigure} is not
		 * already visible.
		 */
		@Override
		public void mouseEntered(MouseEvent event) {
			StringLine sl = (StringLine) event.getSource();

			hideOnExit = false;
			if (!sl.getNoteInfoFigure().isVisible()) {
				sl.getNoteInfoFigure().setBackgroundColor(color);
				sl.getNoteInfoFigure().setVisible(true);

				hideOnExit = true;
			}
		}

		/**
		 * Hides the {@link NoteInfoFigure} figure if it had been shown in
		 * {@link #mouseEntered(MouseEvent)}.
		 */
		@Override
		public void mouseExited(MouseEvent event) {
			StringLine sl = (StringLine) event.getSource();
			if (hideOnExit) {
				sl.getNoteInfoFigure().setVisible(false);
			}
		}

		@Override
		public void mouseDragged(MouseEvent event) {
		}

		@Override
		public void mouseHover(MouseEvent event) {
		}

		@Override
		public void mouseMoved(MouseEvent event) {
		}
	}

}
